package com.dps.recommend.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alicloud.openservices.tablestore.SyncClient;
import com.dps.recommend.beans.Constant;
import com.dps.recommend.beans.DpsRequest;
import com.dps.recommend.beans.RecItemDps;
import com.dps.recommend.third.DpsAliTableUtils;
import com.dps.recommend.third.DpsRedisCache;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j(topic = "dpsRecLog")
public class CommonUtils {


    /**
     * 获取用户的redisKey
     *
     * @param prefix
     * @param userId
     * @param filterDay
     * @return
     */
    public static List<String> getUserRedisKey(String prefix, String userId, String deviceId, Integer filterDay) {
        List<String> userKeys = Lists.newArrayList();
        if (Objects.isNull(filterDay)) {
            userKeys.add(prefix + userId);
        } else {
            for (int i = filterDay - 1; i >= 0; i--) {
                userKeys.add(prefix + userId + "_" + TimeUtil.getTimeDayAgo(i));
                if (!deviceId.equals(userId) && !StringUtils.isEmpty(deviceId)) {
                    userKeys.add(prefix + deviceId + "_" + TimeUtil.getTimeDayAgo(i));
                }
            }
        }
        return userKeys;
    }

    /**
     * 根据userKeys查询redis的数据
     *
     * @param userKeys
     * @return
     */
    public static List<String> getUserFilterRedis(DpsRequest req, List<String> userKeys, DpsRedisCache redisCache) {
        List<Object> impItemIds = redisCache.multiGetPip(req, userKeys);
        LogUtils.info(req, String.format("filter key:%s,data:%s", JSON.toJSONString(userKeys), JSON.toJSONString(impItemIds)));
        List<String> realFilterSet = new ArrayList<>();
        if (!CollectionUtils.isEmpty(impItemIds)) {
            impItemIds.stream().filter(Objects::nonNull).forEach(filterStr -> {
                List<String> itemIdList = Arrays.asList(filterStr.toString().split(",", -1));
                itemIdList = itemIdList.stream().filter(itemId -> !StringUtils.isEmpty(itemId) && !realFilterSet.contains(itemId)).collect(Collectors.toList());
                realFilterSet.addAll(itemIdList);
            });
        }
        return realFilterSet;
    }


    /**
     * 通过反射设置属性的值
     *
     * @param fieldName  属性名
     * @param fieldValue 属性值
     * @param object     实体类对象
     * @throws
     */
    public static void setFieldValueByFieldNameV2(String fieldName, Object fieldValue, Object object) {
        try {
            Field field = null;
            try {
                field = object.getClass().getSuperclass().getDeclaredField(fieldName);
            } catch (Exception e) {
                //log.warn("setFieldValueByFieldNameV2 parent empty");
                field = object.getClass().getDeclaredField(fieldName);

            }
            field.setAccessible(true);
            String type = field.getType().getName();
            if (type.endsWith("String")) {
                field.set(object, fieldValue.toString());
            } else if (type.endsWith("Long") || type.endsWith("long")) {
                field.set(object, Long.valueOf(fieldValue.toString()));
            } else if (type.endsWith("int") || type.endsWith("Integer")) {
                field.set(object, Integer.valueOf(fieldValue.toString()));
            } else if (type.endsWith("byte") || type.endsWith("Byte")) {
                field.set(object, Byte.valueOf(fieldValue.toString()));
            } else if (type.endsWith("float") || type.endsWith("Float")) {
                field.set(object, Float.valueOf(fieldValue.toString()));
            } else if (type.endsWith("double") || type.endsWith("Double")) {
                field.set(object, Double.valueOf(fieldValue.toString()));
            } else if (type.endsWith("boolean") || type.endsWith("Boolean")) {
                field.set(object, Boolean.valueOf(fieldValue.toString()));
            }
        } catch (Exception e) {
            log.error("setFieldValueByFieldNameV2 fieldName:{},fieldValue:{}", fieldName, fieldValue);
        }
    }


    /**
     * 通过反射设置属性的值
     *
     * @param fieldName  属性名
     * @param fieldValue 属性值
     * @param object     实体类对象
     * @throws
     */
    public static void setFieldValueByFieldName(String fieldName, Object fieldValue, Object object) {
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(fieldName, object.getClass());
            Method method = descriptor.getWriteMethod();
            String type = descriptor.getPropertyType().getName();
            if (type.endsWith("String")) {
                method.invoke(object, fieldValue.toString());
            } else if (type.endsWith("Long") || type.endsWith("long")) {
                method.invoke(object, Long.valueOf(fieldValue.toString()));
            } else if (type.endsWith("int") || type.endsWith("Integer")) {
                method.invoke(object, Integer.valueOf(fieldValue.toString()));
            } else if (type.endsWith("byte") || type.endsWith("Byte")) {
                method.invoke(object, Byte.valueOf(fieldValue.toString()));
            } else if (type.endsWith("float") || type.endsWith("Float")) {
                method.invoke(object, Float.valueOf(fieldValue.toString()));
            } else if (type.endsWith("double") || type.endsWith("Double")) {
                method.invoke(object, Double.valueOf(fieldValue.toString()));
            } else if (type.endsWith("boolean") || type.endsWith("Boolean")) {
                method.invoke(object, Boolean.valueOf(fieldValue.toString()));
            }
        } catch (Exception e) {
            log.error("setFieldValueByFieldName fieldName:{},fieldValue:{}", fieldName, fieldValue);
        }
    }

    public static List<Map<String, String>> getAliRowByPkValue(DpsRequest dpsRequest, String pkValue, String tableName, String keyName, SyncClient syncClient, String env) {
        DpsAliTableUtils dpsAliTableUtils = new DpsAliTableUtils(syncClient, env);
        Map<String, String> dpsAliTableMap = dpsAliTableUtils.getRow(dpsRequest, pkValue, tableName, keyName);
        if (CollectionUtils.isEmpty(dpsAliTableMap)) {
            LogUtils.info(dpsRequest, String.format("tableStore data empty tableName=%s,keyName=%s,pkValue=%s", tableName, keyName, pkValue));
        }
        return Arrays.asList(dpsAliTableMap);
    }

    public static List<Map<String, String>> getAliRowByPkValue(DpsRequest dpsRequest, List<String> pkValue, String tableName, String keyName, SyncClient syncClient, String env) {
        DpsAliTableUtils dpsAliTableUtils = new DpsAliTableUtils(syncClient, env);
        Map<String, Map<String, String>> dpsAliTableMap = dpsAliTableUtils.batchGetRow(dpsRequest, tableName, keyName, pkValue);
        if (!CollectionUtils.isEmpty(dpsAliTableMap)) {
            return new ArrayList<>(dpsAliTableMap.values());
        }
        return Lists.newArrayList();
    }


    /**
     * 融合 根据itemId去重复并且取最高分数的数据，并且处理recallVersion为cn以,分割
     *
     * @param list
     * @return
     */
    public static <T extends RecItemDps> List<T> getMerageItemList(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        list = list.stream().filter(Objects::nonNull).sorted(Comparator.comparing(T::getScore).reversed()).collect(Collectors.toMap(T::getItemId, a -> a, (o1, o2) -> {
            String recallVersion = o1.getRecallVersion();
            recallVersion = StringUtils.isEmpty(recallVersion) ? o1.getCn() + "," + o2.getCn() : recallVersion.contains(o1.getCn()) ? recallVersion : recallVersion + "," + o1.getCn();
            recallVersion = StringUtils.isEmpty(recallVersion) ? o1.getCn() + "," + o2.getCn() : recallVersion.contains(o2.getCn()) ? recallVersion : recallVersion + "," + o2.getCn();
            o1.setRecallVersion(recallVersion);
            return o1;
        })).values().stream().map(recItemSquare -> {
            String recallVersion = recItemSquare.getRecallVersion();
            recItemSquare.setRecallVersion(StringUtils.isEmpty(recallVersion) ? recItemSquare.getCn() : recallVersion);
            return recItemSquare;
        }).collect(Collectors.toList());
        return list;
    }

    /**
     * 融合 根据itemId去重复并且取最高分数的数据
     *
     * @param list
     * @return
     */
    public static <T extends RecItemDps> List<T> getMerageTopScoreItemList(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        list = list.stream().sorted(Comparator.comparing(T::getScore).reversed()).collect(Collectors.collectingAndThen(Collectors.toCollection(() ->
                new TreeSet<>(Comparator.comparing(T::getItemId))), ArrayList::new));
        return list;
    }

    /**
     * 融合 根据itemId去重复并且取最高分数的数据
     *
     * @param list
     * @return
     */
    public static <T extends RecItemDps> List<T> getMerageTopScoreItemTypeList(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        list = list.stream().sorted(Comparator.comparing(T::getScore).reversed()).collect(Collectors.collectingAndThen(Collectors.toCollection(() ->
                new TreeSet<>(Comparator.comparing(rec -> {
                    return rec.getItemId() + "_" + rec.getType();
                }))), ArrayList::new));
        return list;
    }

    /**
     * 调用tablestore返回的数据封装
     *
     * @param dpsRequest
     * @param pkValue
     * @param tableJson
     * @param columJson
     * @param targetClassObj
     * @param syncClient
     * @param env
     * @return
     */
    public static List getTableStoreItemList(DpsRequest dpsRequest, List<String> pkValue, String tableName, String keyName, String itemIds, String columJson, Class targetClassObj, SyncClient syncClient, String env) {
        List recItemDpsList = new ArrayList<>();
        if (StringUtils.isEmpty(pkValue)) {
            return recItemDpsList;
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("itemIds", itemIds);
        List<Map<String, String>> aliMapList = CommonUtils.getAliRowByPkValue(dpsRequest, pkValue, tableName, keyName, syncClient, env);
        getAliToEntity(dpsRequest, aliMapList, recItemDpsList, jsonObject, columJson, targetClassObj);
        LogUtils.info(dpsRequest, String.format("tableStore tableName=%s,keyName=%s,pkValue=%s,resp size:%s", tableName, keyName, pkValue, recItemDpsList.size()));
        return recItemDpsList;
    }

    /**
     * 调用tablestore返回的数据封装
     *
     * @param dpsRequest
     * @param pkValue
     * @param tableJson
     * @param columJson
     * @param targetClassObj
     * @param syncClient
     * @param env
     * @return
     */
    public static List getTableStoreItemList(DpsRequest dpsRequest, String pkValue, String tableName, String keyName, String itemIds, String columJson, Class targetClassObj, SyncClient syncClient, String env) {
        List recItemDpsList = new ArrayList<>();
        if (StringUtils.isEmpty(pkValue)) {
            return recItemDpsList;
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("itemIds", itemIds);
        List<Map<String, String>> aliMapList = CommonUtils.getAliRowByPkValue(dpsRequest, pkValue, tableName, keyName, syncClient, env);
        getAliToEntity(dpsRequest, aliMapList, recItemDpsList, jsonObject, columJson, targetClassObj);
        return recItemDpsList;
    }


    /**
     * 调用tablestore返回的数据封装
     *
     * @param dpsRequest
     * @param pkValue
     * @param tableJson
     * @param columJson
     * @param targetClassObj
     * @param syncClient
     * @param env
     * @return
     */
    public static List getTableStoreItemList(DpsRequest dpsRequest, String pkValue, String tableJson, String columJson, Class targetClassObj, SyncClient syncClient, String env) {
        List recItemDpsList = new ArrayList<>();
        if (StringUtils.isEmpty(pkValue)) {
            return recItemDpsList;
        }
        JSONObject jsonObject = JSON.parseObject(tableJson);
        if (CollectionUtils.isEmpty(jsonObject)) {
            return recItemDpsList;
        }
        List<Map<String, String>> aliMapList = CommonUtils.getAliRowByPkValue(dpsRequest, pkValue, (String) jsonObject.getOrDefault("tableName", ""), (String) jsonObject.getOrDefault("keyName", "user_id"), syncClient, env);
        getAliToEntity(dpsRequest, aliMapList, recItemDpsList, jsonObject, columJson, targetClassObj);
        return recItemDpsList;
    }

    /**
     * 调用tablestore返回的数据封装
     *
     * @param dpsRequest
     * @param pkValue
     * @param tableJson
     * @param columJson
     * @param targetClassObj
     * @param syncClient
     * @param env
     * @return
     */
    public static List getTableStoreItemList(DpsRequest dpsRequest, List<String> pkValue, String tableJson, String columJson, Class targetClassObj, SyncClient syncClient, String env) {
        List recItemDpsList = new ArrayList<>();
        if (CollectionUtils.isEmpty(pkValue)) {
            return recItemDpsList;
        }
        JSONObject jsonObject = JSON.parseObject(tableJson);
        if (CollectionUtils.isEmpty(jsonObject)) {
            return recItemDpsList;
        }
        String tableName = (String) jsonObject.getOrDefault("tableName", "");
        String keyName = (String) jsonObject.getOrDefault("keyName", "user_id");
        List<Map<String, String>> aliMapList = CommonUtils.getAliRowByPkValue(dpsRequest, pkValue, tableName, keyName, syncClient, env);
        getAliToEntity(dpsRequest, aliMapList, recItemDpsList, jsonObject, columJson, targetClassObj);
        LogUtils.info(dpsRequest, String.format("tableStore tableName=%s,keyName=%s,pkValue=%s,resp size:%s", tableName, keyName, pkValue, recItemDpsList.size()));
        return recItemDpsList;
    }

    private static void getAliToEntity(DpsRequest dpsRequest, List<Map<String, String>> aliMapList, List recItemDpsList, JSONObject jsonObject, String columJson, Class targetClassObj) {
        aliMapList.stream().forEach(aliMap -> {
            if (CollectionUtils.isEmpty(aliMap)) {
                return;
            }
            String itemIds = aliMap.getOrDefault((String) jsonObject.get("itemIds"), null);
            JSONObject columObject = JSON.parseObject(columJson);
            if (StringUtils.isEmpty(itemIds) && !Objects.isNull(columObject)) {
                return;
            }
            String splitStr = (String) dpsRequest.getRoad().getProperties().getOrDefault("splitStr", "\\^");
            String splitOutStr = (String) dpsRequest.getRoad().getProperties().getOrDefault("splitOutStr", "\\,");
            Arrays.stream(itemIds.split(splitOutStr, -1)).forEach(aliStr -> {
                try {
                    if (StringUtils.isEmpty(aliStr)) {
                        return;
                    }
                    String[] aliArr = aliStr.split(splitStr, -1);
                    Object recItemDps = targetClassObj.newInstance();
                    for (Map.Entry<String, Object> entry : columObject.entrySet()) {
                        String columName = entry.getKey();
                        Integer index = (Integer) entry.getValue();
                        try {
                            CommonUtils.setFieldValueByFieldNameV2(columName, aliArr[index], recItemDps);
                        } catch (Exception e) {
                            LogUtils.error(dpsRequest, String.format("setFieldValueByFieldName error:%s", e));
                        }
                    }
                    recItemDpsList.add(recItemDps);
                } catch (Exception e) {
                    LogUtils.error(dpsRequest, String.format("aliStr:%s to entity error:%s", aliStr, e));
                }
            });
        });
    }


    /**
     * 设置源返回体字段
     *
     * @param recItemDpsList
     * @param request
     * @return
     */
    public static <T extends RecItemDps> List<T> setOrigList(List<T> recItemDpsList, DpsRequest request) {
        LinkedHashMap properties = request.getRoad().getProperties();
        JSONObject json = JSONObject.parseObject(String.valueOf(properties.get("json")));
        if (CollectionUtils.isEmpty(json) || CollectionUtils.isEmpty(recItemDpsList)) {
            return recItemDpsList;
        }
        String cn = (String) json.getOrDefault("cn", null);
        Double score = Double.valueOf((String) json.getOrDefault("score", "0"));
        String type = (String) json.getOrDefault("type", null);
        String recallType = (String) json.getOrDefault("recallType", null);
        String recVersion = (String) json.getOrDefault("recVersion", null);
        String rankType = (String) json.getOrDefault("rankType", null);
        String initVersion = (String) json.getOrDefault("initVersion", null);
        String recallVersion = (String) json.getOrDefault("recallVersion", null);
        String merageVersion = (String) json.getOrDefault("merageVersion", null);
        String widerankVersion = (String) json.getOrDefault("widerankVersion", null);
        String rankVersion = (String) json.getOrDefault("rankVersion", null);
        String postrankVersion = (String) json.getOrDefault("postrankVersion", null);
        Integer size = (Integer) json.getOrDefault("size", -1);
        if (size != -1) {
            recItemDpsList = recItemDpsList.stream().limit(size).collect(Collectors.toList());
        }
        if (score == 0 && StringUtils.isEmpty(cn)
                && StringUtils.isEmpty(recallType) && StringUtils.isEmpty(type)
                && StringUtils.isEmpty(recVersion) && StringUtils.isEmpty(rankType)
                && StringUtils.isEmpty(initVersion) && StringUtils.isEmpty(recallVersion)
                && StringUtils.isEmpty(merageVersion) && StringUtils.isEmpty(widerankVersion)
                && StringUtils.isEmpty(rankVersion) && StringUtils.isEmpty(postrankVersion)) {
            return recItemDpsList;
        }
        recItemDpsList.forEach(recItemDps -> {
            recItemDps.setCn(StringUtils.isEmpty(cn) ? recItemDps.getCn() : cn);
            recItemDps.setScore(recItemDps.getScore() + score);
            recItemDps.setRecallType(StringUtils.isEmpty(recallType) ? recItemDps.getRecallType() : recallType);
            recItemDps.setType(StringUtils.isEmpty(type) ? recItemDps.getType() : type);
            recItemDps.setRecVersion(StringUtils.isEmpty(recVersion) ? recItemDps.getRecVersion() : recVersion);
            recItemDps.setRankType(StringUtils.isEmpty(rankType) ? recItemDps.getRankType() : rankType);
            recItemDps.setInitVersion(StringUtils.isEmpty(initVersion) ? recItemDps.getInitVersion() : initVersion);
            recItemDps.setRecallVersion(StringUtils.isEmpty(recallVersion) ? recItemDps.getRecallVersion() : recallVersion);
            recItemDps.setMerageVersion(StringUtils.isEmpty(merageVersion) ? recItemDps.getMerageVersion() : merageVersion);
            recItemDps.setWiderankVersion(StringUtils.isEmpty(widerankVersion) ? recItemDps.getWiderankVersion() : widerankVersion);
            recItemDps.setRankVersion(StringUtils.isEmpty(rankVersion) ? recItemDps.getRankVersion() : rankVersion);
            recItemDps.setPostrankVersion(StringUtils.isEmpty(postrankVersion) ? recItemDps.getPostrankVersion() : postrankVersion);
        });

        return recItemDpsList;
    }


    /**
     * 曝光过滤
     * userId=req.reqeust.userId(为空则req.reqeust.deviceId)
     * 规则1:day为null时,redisKey=prefix+userId
     * 规则2:day不为空时,redisKey=prefix+userId_day[0-day)
     *
     * @param req      请求
     * @param prefix   前缀
     * @param day      缓存多少天,不加侧传递null
     * @param newItems
     * @return
     */
    public static <T extends RecItemDps> List<T> filterRecItems(DpsRequest req, String prefix, Integer day, List<T> newItems, StringRedisTemplate stringRedisTemplate, Map<String, Object> extMap) {
        String userId = StringUtils.isEmpty(req.getRequest().getUserId()) ? req.getRequest().getDeviceId() : req.getRequest().getUserId();
        LinkedHashMap properties = req.getRoad().getProperties();
        if (StringUtils.isEmpty(prefix) || CollectionUtils.isEmpty(newItems)) {
            //没有配置需要过滤
            return newItems;
        }
        DpsRedisCache redisCache = new DpsRedisCache(stringRedisTemplate);
        List<String> userKeys = CommonUtils.getUserRedisKey(prefix, userId, req.getRequest().getDeviceId(), day);
        List<String> filterList = CommonUtils.getUserFilterRedis(req, userKeys, redisCache);
        LogUtils.info(req, String.format("alread watch size:%s,recallSize:%s", filterList.size(), newItems.size()));
        int maxNum = Integer.valueOf((String) req.getRoad().getProperties().getOrDefault("maxNum", "100000"));
        List<T> recItems = newItems.stream().filter(item -> !filterList.contains(item.getItemId()))
                //.limit(maxNum)
                .collect(Collectors.toList());
        recItems = CollectionUtils.isEmpty(recItems) ? Lists.newArrayList() : recItems;

        boolean isPageOne = (boolean) extMap.getOrDefault("isPageOne", Boolean.FALSE); //第一页循环曝光
        if (!isPageOne) {
            boolean isLoopAndAdd = (boolean) extMap.getOrDefault("isLoopAndAdd", Boolean.TRUE);
            if (isLoopAndAdd) {
                //循环曝光补数据
                if (CollectionUtils.isEmpty(recItems) || recItems.size() < req.getRequest().getPageSize() || filterList.size() > maxNum) {
                    Map<String, T> allItems = newItems.stream().collect(Collectors.toMap(T::getItemId, a -> a, (k1, k2) -> k1));
                    List<String> itemIdFilterList = recItems.stream().map(T::getItemId).collect(Collectors.toList());
                    List<T> newItemLis = filterList.stream()
                            .filter(itemId -> !itemIdFilterList.contains(itemId) && !Objects.isNull(allItems.get(itemId)))
                            .limit(req.getRequest().getPageSize() - recItems.size())
                            .map(itemId -> {
                                return allItems.get(itemId);
                            })
                            .collect(Collectors.toList());

                    redisCache.multiDelPip(req, userKeys);
                    recItems.addAll(newItemLis);
                    LogUtils.info(req, String.format("deviceId:%s isLoopAndAdd delete", req.getRequest().getDeviceId()));
                }
            }

            return recItems;
        } else if (isPageOne) {
            int pageNum = req.getRequest().getPageNum();
            int totalPage = (int) Math.ceil((double) newItems.size() / req.getRequest().getPageSize());

            int currentTotal = req.getRequest().getPageSize();
            if (totalPage == 1 || totalPage > pageNum) {
                currentTotal = req.getRequest().getPageSize();
            } else if (totalPage == pageNum) {
                currentTotal = newItems.size() - ((pageNum - 1) * req.getRequest().getPageSize());
            } else if (totalPage < pageNum) {
                currentTotal = 0;
            }

            int recNum = currentTotal - recItems.size();

            if (pageNum > totalPage) {
                //超出了页面
                redisCache.multiDelPip(req, userKeys);
                return Lists.newArrayList();
            }

            if (totalPage >= pageNum || totalPage == 1) {
                //当前页可能会出现补数据
                if (recNum > 0) {
                    Map<String, T> allItems = newItems.stream().collect(Collectors.toMap(T::getItemId, a -> a, (k1, k2) -> k1));
                    List<String> itemIdFilterList = recItems.stream().map(T::getItemId).collect(Collectors.toList());
                    List<T> newItemLis = filterList.stream()
                            .filter(itemId -> !itemIdFilterList.contains(itemId) && !Objects.isNull(allItems.get(itemId)))
                            .limit(recNum)
                            .map(itemId -> {
                                return allItems.get(itemId);
                            })
                            .collect(Collectors.toList());
                    recItems.addAll(newItemLis);
                }
            }
        }
        return recItems;
    }

    /**
     * 添加假曝光
     * <p>
     * userId=req.reqeust.userId(为空则req.reqeust.deviceId)
     * 规则1:day为null时,redisKey=prefix+userId
     * 规则2:day不为空时,redisKey=prefix+userId_当天时间(yyyy-MM-dd)
     *
     * @param req
     * @param prefix           前缀
     * @param day              过期时间(天) 不传递侧为null
     * @param filterItemIdList
     */
    public static void saveUserImpression(DpsRequest req, String prefix, Integer day, String filterItemIdList, StringRedisTemplate stringRedisTemplate) {
        if (StringUtils.isEmpty(prefix) || StringUtils.isEmpty(filterItemIdList)) {
            return;
        }
        DpsRedisCache redisCache = new DpsRedisCache(stringRedisTemplate);
        String userId = StringUtils.isEmpty(req.getRequest().getUserId()) ? req.getRequest().getDeviceId() : req.getRequest().getUserId();
        String userFilterPre = prefix + userId;
        String userKey = Objects.isNull(day) ? userFilterPre : userFilterPre + "_" + TimeUtil.getTimeDayAgo(0);
        String userHistoryItemIds = redisCache.get(req, userKey);
        if (!StringUtils.isEmpty(userHistoryItemIds)) {
            List<String> filterList = Lists.newArrayList(userHistoryItemIds.split("\\,", -1));
            List<String> filterItemList = Lists.newArrayList(filterItemIdList.split("\\,", -1));
            filterList.removeAll(filterItemList);
            String filterItemIdStr = filterList.stream().collect(Collectors.joining(",")) + "," + filterItemList.stream().collect(Collectors.joining(","));
            filterItemIdList = filterItemIdStr.startsWith(",") ? filterItemIdStr.substring(1, filterItemIdStr.length()) : filterItemIdStr;

        }
//        int maxNum = Integer.valueOf((String) req.getRoad().getProperties().getOrDefault("maxNum", "5000"));
        String[] impItems = filterItemIdList.split("\\,", -1);
//        if (impItems.length > maxNum) {
//            //限制当日最大曝光量上限
//            filterItemIdList = Arrays.stream(impItems)
//                    .skip(impItems.length - maxNum).collect(Collectors.joining(","));
//            LogUtils.info(req, String.format("start to truncate to save to redis for user_id:%s", userId));
//        }
        day = Objects.isNull(day) ? 1 : day;
        redisCache.put(req, userKey, filterItemIdList, day * 60 * 60 * 24);
        LogUtils.info(req, String.format("save redis size:%s", impItems.length));

    }


    /**
     * 时间衰减 initScore*exp(-ratio*时间)
     *
     * @param createTime yyyy-MM-dd HH:mm:ss
     * @param initScore  初始化分数 默认=1.0
     * @param ratio      系数 默认=0.005
     * @return
     */
    public static double getTimeWeightScore(String createTime, Double initScore, Double ratio) {
        ratio = Objects.isNull(ratio) ? 0.005d : ratio;
        initScore = Objects.isNull(initScore) ? 1.0d : initScore;
        Double timeGaps = TimeUtil.getDiffHours(createTime);
        Double timeWeightScore = Objects.isNull(timeGaps) ? 1d : Math.exp(-ratio * timeGaps);
        Double timeGap = timeWeightScore / 1000;
        Double score = (initScore) * timeWeightScore + timeGap;
        BigDecimal bd = new BigDecimal(score);
        score = bd.setScale(6, BigDecimal.ROUND_HALF_UP).doubleValue();
        return score;
    }

    /**
     * 将source集合的数据赋值给target
     *
     * @param source         源List
     * @param target         目标List
     * @param targetClassObj 目标对象类型
     */
    public static void copyList(List source, List target, Class targetClassObj, String[] ignoreArr) {
        source.forEach(item -> {
            try {
                Object data = targetClassObj.newInstance();
                BeanUtils.copyProperties(item, data, ignoreArr);
                target.add(data);
            } catch (Exception e) {
            }
        });
    }


    /**
     * 将source集合的数据赋值给target
     *
     * @param source 源List
     * @param target 目标List
     */
    public static List copyList(DpsRequest dpsRequest, List source, List target, String[] ignoreArr) {
        source.forEach(item -> {
            try {
                Object data = item.getClass().newInstance();
                BeanUtils.copyProperties(item, data, ignoreArr);
                target.add(data);
            } catch (Exception e) {
                LogUtils.error(dpsRequest, String.format("copyList error:%s", e));
            }
        });
        return CollectionUtils.isEmpty(target) ? source : target;
    }

    /**
     * 获取redis的用户曝光
     * userId=req.reqeust.userId(为空则req.reqeust.deviceId)
     * 规则1:day为null时,redisKey=prefix+userId
     * 规则2:day不为空时,redisKey=prefix+userId_day[0-day)
     *
     * @param req      请求
     * @param prefix   前缀
     * @param day      缓存多少天,不加侧传递null
     * @param newItems
     * @return
     */
    public static List<String> getRedisUserExposureItemId(DpsRequest req, String prefix, Integer day, StringRedisTemplate stringRedisTemplate) {
        String userId = StringUtils.isEmpty(req.getRequest().getUserId()) ? req.getRequest().getDeviceId() : req.getRequest().getUserId();
        LinkedHashMap properties = req.getRoad().getProperties();
        if (StringUtils.isEmpty(prefix)) {
            return Lists.newArrayList();
        }
        DpsRedisCache redisCache = new DpsRedisCache(stringRedisTemplate);
        List<String> userKeys = CommonUtils.getUserRedisKey(prefix, userId, req.getRequest().getDeviceId(), day);
        List<String> filterList = CommonUtils.getUserFilterRedis(req, userKeys, redisCache);
        return filterList;
    }

    /**
     * 获取在dps用户的曝光
     *
     * @param req
     * @return
     */
    public static List<String> getDpsUserExposureItemId(DpsRequest dpsRequest) {
        List<String> exposureList = (List<String>) dpsRequest.getExtMap().getOrDefault(Constant.USER_EXPOSURE, null);
        return CollectionUtils.isEmpty(exposureList) ? Lists.newArrayList() : exposureList;
    }
}
